<?php

namespace maoxuner\LaravelJsonRpc\Exceptions;

use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Support\Collection;
use maoxuner\LaravelJsonRpc\Entities\ResponseObject;
use maoxuner\LaravelJsonRpc\Server\Parser;
use Throwable;

class Handler implements ExceptionHandler
{
    protected array $mapping = [];

    protected array $ignores = [];

    public function __construct(
        protected ExceptionHandler $handler,
        protected Parser $parser
    ) {
        $this->mapping = config('jsonrpc.exception.mapping', []);
        $this->ignores = config('jsonrpc.exception.ignores', []);
    }

    protected function map(Throwable $e): Throwable
    {
        foreach ($this->mapping as $from => $to) {
            if (is_a($e, $from)) {
                $e = new $to($e->getMessage(), $e->getCode(), $e);
            }
        }

        return $e;
    }

    public function report(Throwable $e): void
    {
        if ($this->shouldReport($e)) {
            $this->handler->report($e);
        }
    }

    public function shouldReport(Throwable $e): bool
    {
        $e = $this->map($e);

        foreach ($this->ignores as $ignore) {
            if (is_a($e, $ignore)) {
                return false;
            }
        }

        return match (true) {
            $e instanceof ParseException,
            $e instanceof RequestException,
            $e instanceof MethodException,
            $e instanceof ParamsException => false,
            default => true
        };
    }

    public function render($request, Throwable $e)
    {
        try {
            $requestObject = $this->parser->extract($request, false);
            if ($requestObject instanceof Collection) {
                $responseObject = $requestObject->map(fn ($req) => $this->transform($e, $req->id));
            } else {
                $responseObject = $this->transform($e, $requestObject->id);
            }
        } catch (Throwable $throwable) {
            $responseObject = $this->transform($throwable);
        }

        return response()->json($responseObject);
    }

    public function renderForConsole($output, Throwable $e)
    {
        return $this->handler->renderForConsole($output, $e);
    }

    public function transform(Throwable $e, mixed $id = null): ResponseObject
    {
        $e = $this->map($e);

        $exception = match (true) {
            $e instanceof BaseException => $e,
            default => InternalException::build($e)
        };

        return ResponseObject::build($exception, $id);
    }
}
